using System;
using System.Data;
using System.Configuration;
using CodeWork.Library.Data;
using CodeWork.DAL.DataContainers.SqlServer;
using System.Collections.Generic;
using System.Transactions;
using CodeWork.Library;
using System.Data.SqlTypes;
using log4net;
using CodeWork.Library.Web;
using System.Text;

namespace CodeWork.BLL
{
    /// <summary>
    /// This class contains the Business logic for Site user handling
    /// </summary>
    public class UserManager
    {
        public UserManager() { }

        /// <summary>
        /// Checks whether a user id is free for registration or not
        /// </summary>
        /// <param name="userId">User id to check for availability</param>
        /// <returns>True if the user id is free, false otherwise</returns>
        public static bool CheckUserIdAvailability(string userId)
        {
            try
            {
                // Checking for valid format of the user id
                if (string.IsNullOrEmpty(userId) || !RegExUtil.IsCustomMatch(userId, RegExUtil.UserNameFormat))
                    return false;

                // Counting the occurence of the user id
                Siteuser siteUser = new Siteuser();
                List<Condition> conditions = new List<Condition>();
                userId = Security.BindStringInput(userId, SecurityLevel.Two);
                conditions.Add(new Condition("user_id", ConditionOperator.Equal, userId, JoinOperator.FirstValue));
                int count = siteUser.LoadCount(conditions);
                
                // If count is positive, the user id is already taken, otherwise free to register
                if (count > 0)
                    return false;
                else
                    return true;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("UserId check caused server error: Parameters( ");
                logText = logText.Append("userId -> ").Append(userId);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        /// <summary>
        /// This method check the userId and confirmation code combination
        /// and returns the first user that found with the match.
        /// Useful for verification of confirmation mails. This method is
        /// for internal use only. Used by other complicated verification
        /// methods.
        /// </summary>
        /// <param name="userId">The account user id</param>
        /// <param name="confirmationCode">The confirmation that belongs to that user id</param>
        /// <returns>The found user object, otherwise null</returns>
        private static Siteuser VerifyConfirmationCode(string userId, string confirmationCode)
        {
            const int DEFAULT_USER = 0;     // Using the first user, if multiple found
            try
            {
                // Both user id and confirmation code must be non empty
                if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(confirmationCode))
                    return null;

                // Load the users that match the conditions ( userid and confirmation code)
                Siteuser siteUser = new Siteuser();
                List<Condition> conditions = new List<Condition>();

                userId = Security.BindStringInput(userId, SecurityLevel.Two);
                confirmationCode = Security.BindStringInput(confirmationCode, SecurityLevel.Two);

                conditions.Add(new Condition("user_id", ConditionOperator.Equal, userId, JoinOperator.FirstValue));
                conditions.Add(new Condition("confirmation_code", ConditionOperator.Equal, confirmationCode, JoinOperator.And));

                List<Siteuser> siteUsers = siteUser.Load(conditions);

                // If atleast one user is found that match the criteria,
                // return the user or return null
                if (siteUsers.Count > DEFAULT_USER)
                {
                    return siteUsers[DEFAULT_USER];
                }
                return null;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Confirmation code verification caused server error: Parameters( ");
                logText = logText.Append("userId -> ").Append(userId);
                logText = logText.Append(",confirmationCode -> ").Append(confirmationCode);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return null;
            }
        }

        /// <summary>
        /// Verifies that registration confirmation mail and activates the 
        /// user account if the verification is successful
        /// </summary>
        /// <param name="userId">User id for whom the account is being verified</param>
        /// <param name="confirmationCode">Confirmation code for the verification</param>
        /// <returns>True if the account is successfuly verified and activated</returns>
        public static bool VerifyRegistrationConfirmation(string userId, string confirmationCode)
        {
            try
            {
                // Check the confirmation code belongs to that user or not
                Siteuser siteUser = VerifyConfirmationCode(userId, confirmationCode);

                // If the user that match the confirmation code is found
                if (siteUser != null)
                {
                    // Confirm his registration and activate the account
                    siteUser.IsConfirmed = true;
                    siteUser.IsActive = true;
                    return siteUser.Save();
                }
                // send failure if no user is found
                return false;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Registration verification caused server error: Parameters( ");
                logText = logText.Append("userId -> ").Append(userId);
                logText = logText.Append(",confirmationCode -> ").Append(confirmationCode);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        /// <summary>
        /// Creates a new account for a new user. The account is not activated
        /// instantly rather a confirmation mail has been sent and after that
        /// mail is verified with other helper method in this class the user
        /// account is activated.
        /// </summary>
        /// <param name="userId">The user id choosen for the account</param>
        /// <param name="userPassword">Password for the account</param>
        /// <param name="userEmail">Email address for the account</param>
        /// <param name="firstName">First name of the user</param>
        /// <param name="lastName">Last name of the user</param>
        /// <param name="dateOfBirth">Date of birth of the user</param>
        /// <param name="country">Country code of the user</param>
        /// <param name="postalCode">postal code of the user</param>
        /// <param name="phoneNumber">Phone number of the user</param>
        /// <param name="securityQuestion">Security question that is used in case of verification</param>
        /// <param name="securityAnswer">Answer for the security question</param>
        /// <returns></returns>
        public static bool RegisterSiteuser(string userId, string userPassword, string userEmail, string firstName,
            string lastName, DateTime dateOfBirth, string country, string postalCode, string phoneNumber,
            string securityQuestion, string securityAnswer)
        {
            bool result = false;
            const int DEFAULT_SETTINGS = 1;         // Using the default site settings

            // If the user id is still free
            if (CheckUserIdAvailability(userId))
            {
                try
                {
                    // using transaction for consistancy
                    using (TransactionScope transaction = new TransactionScope())
                    {
                        // Filtering all input data for unwanted input
                        userId = Security.BindStringInput(userId, SecurityLevel.Two);
                        userPassword = Security.BindStringInput(userPassword, SecurityLevel.Two);
                        userEmail = Security.BindStringInput(userEmail, SecurityLevel.Two);
                        firstName = Security.BindStringInput(firstName, SecurityLevel.Two);
                        lastName = Security.BindStringInput(lastName, SecurityLevel.Two);
                        country = Security.BindStringInput(country, SecurityLevel.Two);
                        postalCode = Security.BindStringInput(postalCode, SecurityLevel.Two);
                        phoneNumber = Security.BindStringInput(phoneNumber, SecurityLevel.Two);
                        securityQuestion = Security.BindStringInput(securityQuestion, SecurityLevel.Two);
                        securityAnswer = Security.BindStringInput(securityAnswer, SecurityLevel.Two);

                        // Loadind the site settings
                        Sitesettings siteSettings = Sitesettings.Load(DEFAULT_SETTINGS);

                        // Creating a new confirmation code
                        string confirmationCode = Utility.CreateConfirmationCode();
                        // Encrypting the password for storing in database
                        string encryptedPassword = Security.Encrypt(userPassword, siteSettings.SecurityKey, true);

                        // Creating the user account
                        Siteuser siteUser = Siteuser.Create(userId, siteSettings.DefaultUserroleId,
                           encryptedPassword, userEmail, false, false, false, confirmationCode, DateTime.Now,
                           SqlDateTime.MinValue.Value, securityQuestion, securityAnswer);

                        // Also creating the default profile for the user
                        Userprofile userProfile = Userprofile.Create(siteUser.Id, firstName, lastName, dateOfBirth,
                            country, postalCode, phoneNumber);

                        // Sending the registration confirmation mail
                        if (MailManager.SendRegistrationMail(siteSettings, siteUser, userProfile))
                        {
                            // Complete the transaction, everything is perfectly done
                            transaction.Complete();
                            result = true;
                        }
                    }
                    return result;
                }
                catch (Exception ex)
                {
                    // Setting log information about the exception
                    StringBuilder logText = new StringBuilder("Registration caused server error: Parameters( ");
                    logText = logText.Append("userId -> ").Append(userId);
                    logText = logText.Append(",userPassword -> ").Append(userPassword);
                    logText = logText.Append(",userEmail -> ").Append(userEmail);
                    logText = logText.Append(",firstName -> ").Append(firstName);
                    logText = logText.Append(",lastName -> ").Append(lastName);
                    logText = logText.Append(",dateOfBirth -> ").Append(dateOfBirth.ToString());
                    logText = logText.Append(",country -> ").Append(country);
                    logText = logText.Append(",postalCode -> ").Append(postalCode);
                    logText = logText.Append(",phoneNumber -> ").Append(phoneNumber);
                    logText = logText.Append(",securityQuestion -> ").Append(securityQuestion);
                    logText = logText.Append(",securityAnswer -> ").Append(securityAnswer);
                    logText = logText.Append(" )");

                    // Write the log into handled exception list
                    ILog log = LogHelper.GetHandledFileLogger("UserManager");
                    if (log.IsErrorEnabled)
                        log.Error(logText.ToString(), ex);

                    // return failure
                    return false;
                }
            }
            else
                return false;
        }

        /// <summary>
        /// Perform login operation for the users.
        /// </summary>
        /// <param name="userId">user id of the user</param>
        /// <param name="userPassword">password of the user</param>
        /// <returns>True if login is successful, otherwise false</returns>
        public static bool LoginUser(string userId, string userPassword)
        {
            const int DEFAULT_USER = 0;         // Using the first user if multiple found
            const int DEFAULT_SETTINGS = 1;     // Loading the default site settings
            try
            {
                // Loading the site settings
                Sitesettings siteSettings = Sitesettings.Load(DEFAULT_SETTINGS);

                // Loading the user with the specified user id and password
                Siteuser siteUser = new Siteuser();
                List<Condition> conditions = new List<Condition>();

                userId = Security.BindStringInput(userId, SecurityLevel.Two);
                userPassword = Security.Encrypt(Security.BindStringInput(userPassword, SecurityLevel.Two)
                    , siteSettings.SecurityKey, true); 

                conditions.Add(new Condition("user_id", ConditionOperator.Equal, userId, JoinOperator.FirstValue));
                conditions.Add(new Condition("user_password", ConditionOperator.Equal, userPassword, JoinOperator.And));

                List<Siteuser> siteUsers = siteUser.Load(conditions);

                // If at least one user is found
                if (siteUsers.Count > DEFAULT_USER)
                {
                    // Update the last login time of the user
                    siteUsers[DEFAULT_USER].LastLogin = DateTime.Now;

                    // If the user account is successfuly updated
                    if (siteUsers[DEFAULT_USER].Save())
                    {
                        // Load the current user's basic information in the session
                        CurrentUser currentUser = CopyUserToCurrentUser(siteUsers[DEFAULT_USER]);
                        SessionManager.LoggedUser = currentUser;
                        return true;
                    }
                }
                return false;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Login caused server error: Parameters( ");
                logText = logText.Append("userId -> ").Append(userId);
                logText = logText.Append(",userPassword -> ").Append(userPassword);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        /// <summary>
        /// A helper method used by other methods in this class. 
        /// It basically loads the Current user object from 
        /// the site user object. The current user object holds
        /// imporatant data that needs to be frequenty used and
        /// kept in session to save database round trip.
        /// </summary>
        /// <param name="siteUser">Siteuser object that needs to be loaded</param>
        /// <returns>CurrentUser object with loaded data</returns>
        private static CurrentUser CopyUserToCurrentUser(Siteuser siteUser)
        {
            const int DEFAULT_PROFILE = 0;      // Using default profile, in case more is found

            string fullName = "New user";       // Setting the full name as New user in case the profile 
                                                // is not already created

            // If at least one profile is found
            if (siteUser.SiteuserUserprofile.Count > DEFAULT_PROFILE)
            {
                // Update the full name according to the profile information
                fullName = siteUser.SiteuserUserprofile[DEFAULT_PROFILE].FirstName + " " + 
                    siteUser.SiteuserUserprofile[DEFAULT_PROFILE].LastName;
            }

            // Create the CurrentUser object with the data
            CurrentUser currentUser = new CurrentUser(
                siteUser.Id,
                siteUser.UserId,
                siteUser.UserEmail,
                fullName,
                siteUser.LastLogin,
                siteUser.UserroleId);

            return currentUser;
        }

        /// <summary>
        /// Finds a user id and checks sends a confirmation link 
        /// for password change from which user can change their
        /// password.
        /// </summary>
        /// <param name="userId">user id for whom to recover password</param>
        /// <returns>True if password change confirmation mail is sent, false otherwise</returns>
        public static bool RecoverPassword(string userId)
        {
            const int DEFAULT_USER = 0;         // Using the first user as default
            const int DEFAULT_PROFILE = 0;      // Using the first profile as default
            const int DEFAULT_SETTINGS = 1;     // Using the first site settings as default

            bool result = false;
            try
            {
                // Checking whether the user id is in valid format
                if (string.IsNullOrEmpty(userId) || !RegExUtil.IsCustomMatch(userId, RegExUtil.UserNameFormat))
                    return false;

                // Using transaction to maintain consistancy
                using (TransactionScope transaction = new TransactionScope())
                {
                    // Loading the site user that match the user id
                    Siteuser siteUser = new Siteuser();
                    List<Condition> conditions = new List<Condition>();
                    userId = Security.BindStringInput(userId, SecurityLevel.Two);
                    conditions.Add(new Condition("user_id", ConditionOperator.Equal, userId, JoinOperator.FirstValue));
                    List<Siteuser> siteUsers = siteUser.Load(conditions);

                    // If at least one user is found that match the id
                    if (siteUsers.Count > DEFAULT_USER)
                    {
                        // If the account has been verified
                        if (siteUsers[DEFAULT_USER].IsConfirmed)
                        {
                            // Create a new confirmation code for recover password verification mail
                            string confirmationCode = Utility.CreateConfirmationCode();
                            siteUsers[DEFAULT_USER].ConfirmationCode = confirmationCode;

                            // If the user is updated successfuly
                            if (siteUsers[DEFAULT_USER].Save())
                            {
                                // Loading the site settings
                                Sitesettings siteSettings = Sitesettings.Load(DEFAULT_SETTINGS);
                                Userprofile userProfile = null;

                                // If the user account has a profile, load it
                                if (siteUsers[DEFAULT_USER].SiteuserUserprofile.Count > DEFAULT_PROFILE)
                                    userProfile = siteUsers[DEFAULT_USER].SiteuserUserprofile[DEFAULT_PROFILE];

                                // send the password recover verification mail
                                if (MailManager.SendPasswordChangeMail(siteSettings, siteUsers[DEFAULT_USER],
                                    userProfile, true))
                                {
                                    // complete the transaction, every thing is successful
                                    transaction.Complete();
                                    result = true;
                                }
                            }
                        }
                    }
                }
                return result;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Recovery password caused server error: Parameters( ");
                logText = logText.Append("userId -> ").Append(userId);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        /// <summary>
        /// Changes password from password recovery page. Verifies the user confirmation
        /// mail and then sets the new password
        /// </summary>
        /// <param name="userId">User id of the user for whom to change the password</param>
        /// <param name="confirmationCode">confirmation code of the verification mail</param>
        /// <param name="newPassword">New password for the user account</param>
        /// <returns>True if password change succeed, otherwise false</returns>
        public static bool ChangePasswordForPasswordRecovery(string userId, string securityAnswer,
            string confirmationCode, string newPassword)
        {
            const int DEFAULT_PROFILE = 0;          // Using the first profile, if multiple found
            const int DEFAULT_SETTINGS = 1;         // Using the site settings that has Id 1
            bool result = false;
            Userprofile userProfile = null;

            try
            {
                // Using transaction to maintain consistancy
                using (TransactionScope transaction = new TransactionScope())
                {
                    // Testing whether the user id and confirmation code combination is valid
                    Siteuser siteUser = VerifyConfirmationCode(userId, confirmationCode);
                    // Loading the settings for the site.
                    Sitesettings siteSettings = Sitesettings.Load(DEFAULT_SETTINGS);

                    // If the site user is verified and identified
                    if (siteUser != null && siteUser.SecurityAnswer.ToLower() == securityAnswer.ToLower())
                    {
                        // Set a new password for the user
                        siteUser.UserPassword = Security.Encrypt(Security.BindStringInput(newPassword, 
                            SecurityLevel.Two),siteSettings.SecurityKey,true);

                        // Clear the confirmation code to invalidate repeatative request
                        siteUser.ConfirmationCode = string.Empty;

                        // If the user is successfuly saved
                        if (siteUser.Save())
                        {
                            // Collect the profile of the user
                            if (siteUser.SiteuserUserprofile.Count > DEFAULT_PROFILE)
                                userProfile = siteUser.SiteuserUserprofile[DEFAULT_PROFILE];

                            // Send password change successful message and with new password 
                            if (MailManager.SendPasswordChangeInformationMail(siteSettings, siteUser,
                                userProfile))
                            {
                                // Complete the transaction, every thing is properly done
                                transaction.Complete();
                                result = true;
                            }
                        }
                    }
                }
                return result;
            }
            catch (Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Registration verification caused server error: Parameters( ");
                logText = logText.Append("userId -> ").Append(userId);
                logText = logText.Append(",confirmationCode -> ").Append(confirmationCode);
                logText = logText.Append(",newPassword -> ").Append(newPassword);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return false;
            }
        }

        public static string GetSecurityQuestion(string userId)
        {
            const int DEFAULT_USER = 0;         // Using the first user as default

            try
            {
                // Loading the site user that match the user id
                Siteuser siteUser = new Siteuser();
                List<Condition> conditions = new List<Condition>();
                userId = Security.BindStringInput(userId, SecurityLevel.Two);
                conditions.Add(new Condition("user_id", ConditionOperator.Equal, userId, JoinOperator.FirstValue));
                List<Siteuser> siteUsers = siteUser.Load(conditions);

                // If at least one user is found
                if (siteUsers.Count > DEFAULT_USER)
                    return siteUsers[DEFAULT_USER].SecurityQuestion;
                else
                    return null;
            }
            catch(Exception ex)
            {
                // Setting log information about the exception
                StringBuilder logText = new StringBuilder("Security question retrive server error: Parameters( ");
                logText = logText.Append("userId -> ").Append(userId);
                logText = logText.Append(" )");

                // Write the log into handled exception list
                ILog log = LogHelper.GetHandledFileLogger("UserManager");
                if (log.IsErrorEnabled)
                    log.Error(logText.ToString(), ex);

                // return failure
                return null;
            }
        }
    }
}